﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.Collections;

using Noris.Tools.TraceVisualiser.Support;

namespace Noris.Tools.TraceVisualiser.Components
{
	#region CLASS GridNodeRowCls : data jednoho nodu v síti Tree, základ pro konstrukci stromu s prvky s více Parenty i více Childs
	/// <summary>
	/// GridNodeRowCls : data jednoho nodu v síti Tree, základ pro konstrukci stromu s prvky s více Parenty i více Childs.
	/// Protože se jeden datový prvek může vyskytovat na více řádcích grafu, a může mít různě zavřené / otevřené nody, 
	/// musí existovat více instancí této třídy GridNodeRowCls = pro každý výskyt jedna.
	/// Rozdíl mezi třídou GridDataRowCls a GridNodeRowCls:
	/// Třída GridDataRowCls rozšiřuje data IDataRow o paměť elementů (a jejich časové přepočty).
	/// Třída GridNodeRowCls představuje jednu vizuální pozici datového řádku (kterých může být více) ve strumu struktury.
	/// </summary>
	internal class GridNodeRowCls
	{
		#region KONSTRUKCE
		private GridNodeRowCls(int id, GridDataRowCls dataRow, GridNodeRowCls parent, bool subNodesExists)
		{
			this.Id = id;
            this.DataRow = dataRow;
			this.Parent = parent;
			this.Childs = new List<GridNodeRowCls>();
            this.SubNodesState = (subNodesExists ? SubNodesStates.NotLoaded : SubNodesStates.NotExists);
			this.Expanded = false;
		}
		/// <summary>
		/// Vizualizace
		/// </summary>
		/// <returns></returns>
		public override string ToString()
		{
			return "{GID=" + this.GId.ToString() +
				"; ID=" + this.Id.ToString() +
				"; Parent" + (this.Parent == null ? ": none" : "ID=" + this.Parent.Id.ToString()) + "; ChildCount=" +
				this.Childs.Count.ToString() + "}";
		}
		#endregion
		#region TVORBA OBJEKTU
        internal static GridNodeRowCls Create(int id, GridDataRowCls dataRow, GridNodeRowCls parent)
        {
            return new GridNodeRowCls(id, dataRow, parent, dataRow.RowItem.HasSubNodes);
        }
        internal static GridNodeRowCls Create(int id, GridDataRowCls dataRow)
        {
            return new GridNodeRowCls(id, dataRow, null, dataRow.RowItem.HasSubNodes);
        }
		#endregion
		#region PROPERTY
		/// <summary>
		/// ID tohoto záznamu = jednoznačný klíč ke konkrétnímu řádku. Po dobu živata grafu 
		/// je neměnné bez ohledu na aktuální viditelnost / neviditelnost řádku, 
		/// a je unikátní bez ohledu na vícenásobný výskyt řádků jednoho GIDu.
		/// ID začínají 1. 0 je vyhrazena jako neexistující ID (viz ParentId).
		/// </summary>
		internal int Id { get; private set; }
        /// <summary>
        /// Řádek s daty tohoto nodu. Node je vizuální uzel stromu, Data jsou řádek z datového zdroje. 
        /// Jeden řádek s Daty může být zobrazen na více Nodech. 
        /// Jeden Node má pouze jeden Datový řádek.
        /// </summary>
        internal GridDataRowCls DataRow { get; private set; }
		/// <summary>
		/// Node záznamu Parent, vede na konkrétní řádek Parenta (když by zde byl GID, tak nelze určit který řádek tohoto GIDu to je).
		/// Jeden GID může být zobrazen na více řádkách.
		/// Pokud je zde 0, pak tento řádek nemá Parenta, a je zobrazen v prvním Listu (pak je IsTopLevelRow == true).
		/// </summary>
        internal GridNodeRowCls Parent { get; private set; }
		/// <summary>
		/// Moje děti (jeden řádek může mít více dětí).
		/// Obsahuje reference položek Child, nikoli jejich GID nebo NodeID.
		/// </summary>
        internal List<GridNodeRowCls> Childs { get; private set; }

		/// <summary>
		/// GID tohoto záznamu = řádek.
		/// V záznamu této třídy nejsou uložena přímo data řádku, ale přes GID jsou snadno k dohledání.
		/// </summary>
        internal GID GId { get { return this.DataRow.GId; } }
        /// <summary>
        /// Jméno nodu. Mělo by být unikátní, zatím ale unikátnost není kontrolována. Používá se při persistenci pointerů na řádek(řádky).
        /// GID se může při příštím reloadu dat změnit, ale NodeName by nemělo.
        /// </summary>
        internal string NodeName { get { return this.DataRow.NodeName; } }
        /// <summary>
        /// Plné jméno tohoto nodu, obsahuje jména nodů všech parentů. Oddělena jsou zpětným lomítkem.
        /// FullNodeName začíná nejvyšším parentem, a jde dolů až k this nodu, tak jako FullPath v adresářích.
        /// </summary>
        internal string FullNodeName
        {
            get 
            {
                string parentNodeName = (this.HasParent ? this.Parent.FullNodeName + @"\" : "");
                return parentNodeName + this.NodeName;
            }
        }
        /// <summary>
        /// Příznak true, pokud tento node má parenta.
        /// </summary>
        internal bool HasParent { get { return (this.Parent != null); } }
        /// <summary>
		/// Příznak, zda tento řádek je / není v TopLevel seznamu řádků.
		/// true = řádek má být vypsán v seznamu první úrovně / false = ne, vždy je jen jako Child pro nějaký jiný řádek.
		/// Řádek je v TopLevel tehdy, když z datového zdroje přijde bez zadaného parenta.
		/// </summary>
		internal bool IsTopLevelRow { get { return this.Parent == null; } }
		/// <summary>
		/// Příznak, že tento řádek obsahuje nějaký (alespoň jeden) odkaz na Childs řádky
		/// </summary>
		internal bool HasChilds { get { return this.Childs != null && this.Childs.Count > 0; } }
        /// <summary>
        /// Stav kolekce subnodů. Stav nelze měnit napřímo, ale pomocí odpovídajících metod.
        /// Stav neodráží hodnotu Expanded (zda jsou subnody rozbalené nebo ne), pouze stav jejich existence a načítání.
        /// </summary>
        internal SubNodesStates SubNodesState { get; private set; }
        /// <summary>
		/// Tento řádek má v současné době své subnodes viditelné (rozbalené)
		/// </summary>
        internal bool Expanded { get; set; }
		/// <summary>
		/// Obsahuje true, pokud tento node je viditelný z hlediska otevření/zavření nodů.
		/// Tento node je viditelný, když jeho parent je viditelný a otevřený.
		/// Pokud node nemá parenta, pak se bere za viditelný (tím je zřejmé, že se neuplatňuje řádkový filtr, ale jen otevření/zavření nodů).
		/// </summary>
		internal bool ThisNodeVisible
		{
			get
			{
				if (this.Parent == null) return true;         // Parent není => já jsem TopLevel => jsem vidět.

				// Parent existuje => já jsem vidět, když můj parent je vidět a když je otevřený:
                return (this.Parent.Expanded && this.Parent.ThisNodeVisible);
			}
		}
		#endregion
        #region FILTER PROPERTY
        /// <summary>
        /// Tento řádek může být zobrazen v seznamu, protože vyhovuje filtru on sám, nebo jeho parent, nebo některý z jeho Childs.
        /// </summary>
        internal bool NodeIsVisible { get { return (this.DataRowIsVisibleByFilter || this.NodeIsVisibleFromParent || this.NodeIsVisibleFromChild); } }

        /// <summary>
        /// Tento řádek sám je vybrán filtrem jako viditelný (osobně vyhovuje filtru) (je uvedeno na DataRow.RowIsVisibleByFilter).
        /// </summary>
        internal bool DataRowIsVisibleByFilter { get { return this.DataRow.RowIsVisibleByFilter; } }
        /// <summary>
        /// Tento řádek má být zobrazen, protože jeho Parent vyhovuje filtru, anebo našeho Parenta některý další Parent vyhovuje filtru.
        /// </summary>
        internal bool NodeIsVisibleFromParent { get { return (this.Parent != null && (this.Parent.DataRowIsVisibleByFilter || this.Parent.NodeIsVisibleFromParent)); } }
        /// <summary>
        /// Tento řádek má být zobrazen, protože některý z jeho Childs je vyhovuje filtru, anebo některý z jeho Grand-Childs vyhovuje filtru.
        /// </summary>
        internal bool NodeIsVisibleFromChild
        {
            get
            {
                // Hodnota z cache:
                if (!this._NodeIsVisibleFromChild.HasValue)
                {   // Cache je neplatná:
                    if (this.HasChilds)
                        // Tady se může rozběhnout pěkná rekurze (this.Childs[].NodeIsVisibleFromChild) :
                        this._NodeIsVisibleFromChild = this.Childs.Exists(c => (c.DataRowIsVisibleByFilter || c.NodeIsVisibleFromChild));
                    else
                        this._NodeIsVisibleFromChild = false;
                }
                return this._NodeIsVisibleFromChild.Value;
            }
        }
        #region Cachování příznaku, že některý SubNode je viditelný (NodeIsVisibleFromChild)
        /// <summary>
        /// Sumární příznak viditelnosti subnodes (NodeIsVisibleFromChild)
        /// </summary>
        private bool? _NodeIsVisibleFromChild;
        /// <summary>
        /// Invaliduje interní cache, kde je uložen sumární příznak viditelnosti subnodes (NodeIsVisibleFromChild)
        /// </summary>
        internal void NodeIsVisibleFromChildInvalidate()
        {
            this._NodeIsVisibleFromChild = null;
        }
        #endregion
        #endregion
        #region STAVY SUBNODES: DETEKCE, METODY PRO ZMĚNU HODNOTY
        /// <summary>
        /// Příznak, zda lze rozbalovat kolekci subnodů.
        /// To lze tehdy, když subnody existují, nebo se načítají, nebo se budou načítat (stavy: NotLoaded, NowLoading, Invalid, WasteLoading, Loaded).
        /// Hodnota nezáleží na současném stavu rozbalení this.Expanded
        /// </summary>
        internal bool SubnodesExpandable
        {
            get
            {
                SubNodesStates s = this.SubNodesState;
                return s == SubNodesStates.NotLoaded || 
                       s == SubNodesStates.NowLoading ||
                       s == SubNodesStates.Invalid ||
                       s == SubNodesStates.WasteLoading ||
                       s == SubNodesStates.Loaded;
            }
        }
        /// <summary>
        /// Příznak, že právě nyní je dostupná kolekce platných subnodes.
        /// Ta je dostupná pouze ve stavu Loaded. Pokud je kolekce dosud nenačtená, anebo je nevalidní nebo se právě načítá, 
        /// pak kolekce není platná.
        /// </summary>
        internal bool SubnodesContainValidChilds
        {
            get
            {
                SubNodesStates s = this.SubNodesState;
                return s == SubNodesStates.Loaded;
            }
        }
        /// <summary>
        /// Příznak, že subnody je třeba načíst (stav: NotLoaded, Invalid).
        /// Pozor z hlediska mutlithreadingu: tato property je "jednoduchá", zjistí zda je třeba nody načíst, 
        /// ale by pokud dva thready na základě toho chtěly načítat data, mohly by se k tomu rozhodnout oba současně.
        /// K tomu je vhodnější využít metodu SubnodesNeedLoadStart(), která je threadově bezpečná
        /// a zajistí přepnutí stavu na hodnotu NowLoading, pak teprve uvolní zámek a vrátí true.
        /// </summary>
        internal bool SubnodesNeedLoad
        {
            get
            {
                SubNodesStates s = this.SubNodesState;
                return s == SubNodesStates.NotLoaded ||
                       s == SubNodesStates.Invalid;
            }
        }
        /// <summary>
        /// Metoda, která detekuje potřebu načítat Subnodes (stejně jako property this.SubnodesNeedLoad),
        /// ale detekuje ji po zámkem (aby její hodnota byla multithreadově spolehlivá).
        /// Pokud je třeba načítat data (vrací se true), pak stále ještě pod zámkem nastaví hodnotu this.SubNodesState = SubNodesStates.NowLoading;
        /// Pozor: po této metodě je nezbytné načítání dokončit, jinak zůstane řádek ve stavu NowLoading, a víc s ním neuděláme.
        /// Je tedy třeba zpracovat i chyby v procesu načítání a řádek alespoň odblokovat.
        /// Řádek se odblokuje tímto postupem: vloží se do něj nové subndy, a zavolá se metoda SubnodesLoaded().
        /// </summary>
        /// <returns></returns>
        internal bool SubnodesNeedLoadStart()
        {
            lock (_StateLocker)
            {
                bool needLoad = this.SubnodesNeedLoad;
                if (needLoad)
                    this.SubNodesState = SubNodesStates.NowLoading;
                return needLoad;
            }
        }
        /// <summary>
        /// Metoda volaná povinně po dokončení každého načítání subnodů, po jejich uložení do this.Childs.
        /// Podle aktuálního stavu určí následující stav: 
        ///  - ze stavu NowLoading přejde do stavu Loaded nebo NotExists (podle počtu položek v this.Childs),
        ///  - ze stavu WasteLoading přejde do stavu Invalid.
        /// </summary>
		internal void SubnodesLoaded()
		{
			this.SubnodesLoaded(this.Childs.Count);
		}
        /// <summary>
        /// Metoda volaná povinně po dokončení každého načítání subnodů, po jejich uložení do this.Childs.
        /// Podle aktuálního stavu určí následující stav: 
        ///  - ze stavu NowLoading přejde do stavu Loaded nebo NotExists (podle počtu položek v this.Childs),
        ///  - ze stavu WasteLoading přejde do stavu Invalid.
        /// </summary>
		internal void SubnodesLoaded(int subNodesCount)
        {
            lock (_StateLocker)
            {
                if (this.SubNodesState == SubNodesStates.WasteLoading)
                    this.SubNodesState = SubNodesStates.Invalid;
                else if (subNodesCount == 0)
                    this.SubNodesState = SubNodesStates.NotExists;
                else
                    this.SubNodesState = SubNodesStates.Loaded;
            }
        }
        /// <summary>
        /// Metoda, která zajistí invalidaci kolekce Childs.
        /// Tím je zajištěno, že při nejbližší potřebě těchto subnodů bude kolekce znovu načtena.
        /// Invalidace funguje i uprostřed procesu načítání subnodů (ze stavu NowLoading přejde do stavu WasteLoading).
        /// </summary>
        internal void SubnodesInvalidate()
        {
            lock (_StateLocker)
            {
                if (this.SubNodesState == SubNodesStates.NowLoading)
                    this.SubNodesState = SubNodesStates.WasteLoading;
                else
                    this.SubNodesState = SubNodesStates.Invalid;
            }
        }
        /// <summary>
        /// Metoda nastaví, že tento node nemá žádné Subnodes.
        /// Pokud ale tento node má subnodes (this.HasChilds), pak nic nedělá.
        /// Rovněž pokud právě probíhá načtení, nebo je stav invalidován, pak stav nemění.
        /// Tedy: do stavu NotExists se dostane pouze ze stavu None, NotLoaded nebo Loaded, a to za podmínky HasChilds.
        /// </summary>
        internal void SubnodesSetNotExists()
        {
            if (this.HasChilds) return;
            lock (_StateLocker)
            {
                SubNodesStates s = this.SubNodesState;
                if (s == SubNodesStates.None ||
                    s == SubNodesStates.NotLoaded ||
                    s == SubNodesStates.Loaded)
                    this.SubNodesState = SubNodesStates.NotExists;
            }
        }
        internal SubNodesTreeIconType SubNodesTreeIcon;
        /// <summary>
        /// Zámek, který 
        /// </summary>
        private object _StateLocker = new object();
        #endregion
        #region INTERNAL METODY
        /// <summary>
		/// Metoda zjistí, zda ve svých Childs obsahuje záznam (daný GID)
		/// </summary>
		/// <param name="gId">GID, na který se ptáme</param>
		/// <returns></returns>
		internal bool ContainChild(GID gId)
		{
			return this._ContainChild(gId);
		}
		/// <summary>
		/// Přidá daný ID do soupisu mých Child (děti, podřízené prvky).
		/// Jeden řádek může mít více childs.
		/// Je to dané konstrukcí dat: jedna poptávka se může vyrábět ve více krocích (strom rozpadu dílce).
		/// Každý podřízený prvek je jeden vyráběný dílec.
		/// </summary>
		/// <param name="child">Prvek Child</param>
		internal void AddChild(GridNodeRowCls child)
		{
			if (!this._ContainChild(child.GId))
			{
				// V některých případech může být v datech řádku (IDataRow.NextGId) uložen GID řádku, který je v téže úrovni jako nově vkládaný řádek,
				//  a PŘED který se má nově ukládaný řádek vložit.
				// Pokud je NextGId zadán, a ve zdejším seznamu jej naleznu, pak nový řádek vložím před něj, jinak jej přidám na konec:
				GID nextGId = child.DataRow.RowItem.NextGId;
				if (!nextGId.IsEmpty)
				{
					int nextIdx = this.Childs.FindIndex(node => node.GId == nextGId);
					if (nextIdx >= 0)
						this.Childs.Insert(nextIdx, child);
					else
						this.Childs.Add(child);
				}
				else
					this.Childs.Add(child);
			}
		}
		/// <summary>
		/// Odebere ze svých Child nodes ty položky s daným GIDem.
		/// </summary>
		/// <param name="gID"></param>
		internal void RemoveChild(GID gID)
		{
			this.Childs.RemoveAll(node => node.GId == gID);
		}
		/// <summary>
		/// Určí typ ikony přímo před tímto prvkem.
		/// Reaguje na své příznaky.
		/// </summary>
		/// <returns></returns>
		internal TreeLineType GetTreeIcon(TreeRowPosition rowPosition)
		{
			bool isLastRow = (rowPosition == TreeRowPosition.Last || rowPosition == TreeRowPosition.Single);
			bool subNodesExp = this.SubnodesExpandable;
			bool subNodesVis = this.Expanded;
			if (isLastRow)
			{	// Tento node je poslední ve své řadě:
				if (!subNodesExp) return TreeLineType.Turn;                      // Poslední ve své řadě, aktuální, bez subnodes => holá odbočka
				if (subNodesVis && subNodesExp) return TreeLineType.MinusTurn;   // ..., aktuální, se subnodes, otevřený => MINUS a odbočka
				return TreeLineType.PlusTurn;                                    // ..., aktuální, se subnodes, zavřený => PLUS a odbočka
			}
			else
			{	// Tento node není poslední, pod ním jsou další, budeme kreslit nějaký druh pokračovací čáry:
				if (!subNodesExp) return TreeLineType.Branch;                    // Neposlední, aktuální, bez subnodes => čára o větev
				if (subNodesVis && subNodesExp) return TreeLineType.MinusBranch; // ..., aktuální, se subnodes, otevřený => čára, MINUS a odbočka
				return TreeLineType.PlusBranch;                                  // ..., aktuální, se subnodes, zavvřený => čára, PLUS a odbočka
			}
		}
		/// <summary>
		/// Určí typ čáry, která je před ikonami mých subnodů (pokud jsem poslední, tak nic, a pokud nejsem poslední, tak holá čára dolů).
		/// Reaguje na své příznaky.
		/// </summary>
		/// <returns></returns>
		internal TreeLineType GetTreeLine(TreeRowPosition rowPosition)
		{
			if ((rowPosition == TreeRowPosition.Last))
				return TreeLineType.Blank;
			else
				return TreeLineType.Line;
		}
		#endregion
		#region PRIVATE METODY
		/// <summary>
		/// Metoda zjistí, zda ve svých Childs obsahuje záznam (daný GID)
		/// </summary>
		/// <param name="gId">GID, na který se ptáme</param>
		/// <returns></returns>
		private bool _ContainChild(GID gId)
		{
			return this.Childs.Exists(delegate(GridNodeRowCls c) { return (c.GId == gId); });
		}
		#endregion
		#region EXPAND / COLLAPSE NODU, VČETNĚ CHILDS
		/// <summary>
		/// Rozbalí svůj node, případně i své Childs, a nastaví příznak skutečně provedené změny.
		/// (Pokud je požadován Expand, a tento node nemá childs, pak se neprovede a změna nenastane. 
		/// Anebo pokud je již rozbalený, tak taky změna nenastane).
		/// </summary>
		internal void NodeExpand()
		{
			bool anyNodeIsChanged = false;
			this._NodeExpand(false, ref anyNodeIsChanged);
		}
		/// <summary>
		/// Rozbalí svůj node, případně i své Childs, a nastaví příznak skutečně provedené změny.
		/// (Pokud je požadován Expand, a tento node nemá childs, pak se neprovede a změna nenastane. 
		/// Anebo pokud je již rozbalený, tak taky změna nenastane).
		/// </summary>
		/// <param name="withChilds">Požadavek na Expand všech Childs</param>
		/// <param name="anyNodeIsChanged">Příznak realizované změny. Při změně se nastaví na true, pokud není změna tak se nemění.</param>
		internal void NodeExpand(bool withChilds)
		{
			bool anyNodeIsChanged = false;
			this._NodeExpand(withChilds, ref anyNodeIsChanged);
		}
		/// <summary>
		/// Rozbalí svůj node, případně i své Childs, a nastaví příznak skutečně provedené změny.
		/// (Pokud je požadován Expand, a tento node nemá childs, pak se neprovede a změna nenastane. 
		/// Anebo pokud je již rozbalený, tak taky změna nenastane).
		/// </summary>
		/// <param name="withChilds">Požadavek na Expand všech Childs</param>
		/// <param name="anyNodeIsChanged">Příznak realizované změny. Při změně se nastaví na true, pokud není změna tak se nemění.</param>
		internal void NodeExpand(bool withChilds, ref bool anyNodeIsChanged)
		{
			this._NodeExpand(withChilds, ref anyNodeIsChanged);
		}
		/// <summary>
		/// Rozbalí svůj node, případně i své Childs, a nastaví příznak skutečně provedené změny.
		/// (Pokud je požadován Expand, a tento node nemá childs, pak se neprovede a změna nenastane. 
		/// Anebo pokud je již rozbalený, tak taky změna nenastane).
		/// </summary>
		/// <param name="withChilds">Požadavek na Expand všech Childs</param>
		/// <param name="anyNodeIsChanged">Příznak realizované změny. Při změně se nastaví na true, pokud není změna tak se nemění.</param>
		private void _NodeExpand(bool withChilds, ref bool anyNodeIsChanged)
		{
			// Expandovat this?
			if (!this.Expanded)
			{
                if (this.SubnodesContainValidChilds)
                {	// Pokud nody existují, a jsou načteny, pak se rozbalím:
                    this.Expanded = true;
                    anyNodeIsChanged = true;
                }
			}

			// Expandovat Childs?
			if (this.HasChilds && withChilds)
			{
				foreach (GridNodeRowCls child in this.Childs)
					child.NodeExpand(withChilds, ref anyNodeIsChanged);
			}
		}
		/// <summary>
		/// Zabalí svůj node, případně i své Childs, a nastaví příznak skutečně provedené změny.
		/// (Pokud je požadován Collapse, a tento node nemá childs, pak se neprovede a změna nenastane. 
		/// Anebo pokud je již sbalený, tak taky změna nenastane).
		/// </summary>
		internal void NodeCollapse()
		{
			bool anyNodeIsChanged = false;
			this._NodeCollapse(false, ref anyNodeIsChanged);
		}
		/// <summary>
		/// Zabalí svůj node, případně i své Childs, a nastaví příznak skutečně provedené změny.
		/// (Pokud je požadován Collapse, a tento node nemá childs, pak se neprovede a změna nenastane. 
		/// Anebo pokud je již sbalený, tak taky změna nenastane).
		/// </summary>
		/// <param name="withChilds">Požadavek na Collapse všech Childs</param>
		/// <param name="anyNodeIsChanged">Příznak realizované změny. Při změně se nastaví na true, pokud není změna tak se nemění.</param>
		internal void NodeCollapse(bool withChilds)
		{
			bool anyNodeIsChanged = false;
			this._NodeCollapse(withChilds, ref anyNodeIsChanged);
		}
		/// <summary>
		/// Zabalí svůj node, případně i své Childs, a nastaví příznak skutečně provedené změny.
		/// (Pokud je požadován Collapse, a tento node nemá childs, pak se neprovede a změna nenastane. 
		/// Anebo pokud je již sbalený, tak taky změna nenastane).
		/// </summary>
		/// <param name="withChilds">Požadavek na Collapse všech Childs</param>
		/// <param name="anyNodeIsChanged">Příznak realizované změny. Při změně se nastaví na true, pokud není změna tak se nemění.</param>
		internal void NodeCollapse(bool withChilds, ref bool anyNodeIsChanged)
		{
			this._NodeCollapse(withChilds, ref anyNodeIsChanged);
		}
		/// <summary>
		/// Zabalí svůj node, případně i své Childs, a nastaví příznak skutečně provedené změny.
		/// (Pokud je požadován Collapse, a tento node nemá childs, pak se neprovede a změna nenastane. 
		/// Anebo pokud je již sbalený, tak taky změna nenastane).
		/// </summary>
		/// <param name="withChilds">Požadavek na Collapse všech Childs</param>
		/// <param name="anyNodeIsChanged">Příznak realizované změny. Při změně se nastaví na true, pokud není změna tak se nemění.</param>
		private void _NodeCollapse(bool withChilds, ref bool anyNodeIsChanged)
		{
			// Sbalit this?
			if (this.Expanded)
			{
				this.Expanded = false;
				anyNodeIsChanged = true;
			}

			// Sbalit i Childs?
			if (this.HasChilds && withChilds)
			{
				foreach (GridNodeRowCls child in this.Childs)
					child.NodeCollapse(withChilds, ref anyNodeIsChanged);
			}
		}
		#endregion
        #region EXPAND ALL PARENTS
        /// <summary>
        /// Metoda zajistí, že všechny moje Parenty budou otevřené (expandované), takže this node bude zobrazen v tree bez nutnosti rozbalovat parent nody ručně.
        /// </summary>
        internal void ExpandAllParents()
        {
            if (this.HasParent)
            {
                this.Parent.Expanded = true;             // Zajistím, aby můj parent byl otevřen
                this.Parent.ExpandAllParents();          // Zajistím totéž i pro mého Parenta (rekurzivně), dokud on bude mít svého parenta.
            }
        }
        #endregion
	}
	#endregion
	#region STRUCT RowID : kombinace GID a NodeID, jednoznačná a rychlá identifikace řádku z hlediska vizuálního i datového
	/// <summary>
	/// Struktura RowID : kombinace GID a NodeID, jednoznačná a rychlá identifikace řádku z hlediska vizuálního i datového.
	/// Struktura RowID může být použita jako klíč v Hashtable a Dictionary.
	/// Pracuje s NodeId, což je unikátní klíč (nepracuje se s kombinací NodeId + GID, protože NodeId je dostatečně unikátní).
	/// </summary>
	internal struct RowID
	{
		internal RowID(GID gId, int nodeId)
		{
			this._GId = gId;
			this._NodeId = nodeId;
		}
		private GID _GId;
		private int _NodeId;
		/// <summary>
		/// Vizualizace
		/// </summary>
		/// <returns></returns>
		public override string ToString()
		{
			return "{NodeId=" + this._NodeId.ToString() + "; GID=" + this._GId.ToString() + "}";
		}
		/// <summary>
		/// GID řádku = dataová identifikace, nezmění se ani po přenačtení grafu. 
		/// Změní se jen připojením k jiným datům nebo po nové přípravě dat.
		/// Pozor: jeden GID (datový řádek) může být zobrazen na více řádcích vizuálních.
		/// Z vizuálního hlediska je GID nedostatečný, neurčuje jeden konkrétní řádek.
		/// </summary>
		internal GID GId { get { return _GId; } }
		/// <summary>
		/// NodeID řádku, je konstantní podobně jako GID. Jednoznačná identifikace vizuálního řádku.
		/// Nemění se při otevírání / zavírání nodů, ani po donačtení dalších podřízených nodů.
		/// Změní se po Clear grafu.
		/// První NodeId je 1, kdežto 0 značí neurčeno.
		/// </summary>
		internal int NodeId { get { return _NodeId; } }
		#region Hashcode, Equals
		/// <summary>
		/// Vrací Hashcode
		/// </summary>
		/// <returns></returns>
		public override int GetHashCode()
		{
			return this._NodeId.GetHashCode();
		}
		/// <summary>
		/// Vrací Equals
		/// </summary>
		/// <param name="obj"></param>
		/// <returns></returns>
		public override bool Equals(object obj)
		{
			if (!(obj is RowID)) return false;
			return (RowID._IsEqual(this, (RowID)obj));
		}
		#endregion
		#region operator ==, !=, IComparable)
		/// <summary>
		/// Operátor "je rovno"
		/// </summary>
		/// <param name="a"></param>
		/// <param name="b"></param>
		/// <returns></returns>
		public static bool operator ==(RowID a, RowID b)
		{
			return RowID._IsEqual(a, b);
		}
		/// <summary>
		/// Operátor "není rovno"
		/// </summary>
		/// <param name="a"></param>
		/// <param name="b"></param>
		/// <returns></returns>
		public static bool operator !=(RowID a, RowID b)
		{
			return (!RowID._IsEqual(a, b));
		}
		/// <summary>
		/// Porovnání dvou instancí této struktury, zda obsahují shodná data
		/// </summary>
		/// <param name="a"></param>
		/// <param name="b"></param>
		/// <returns></returns>
		private static bool _IsEqual(RowID a, RowID b)
		{
			bool dataEqual = (a._NodeId == b._NodeId);
			return dataEqual;
		}
		#endregion
	}
	#endregion
	#region DELEGÁTI A EVENTARGS PRO GridDataCls
	/// <summary>
	/// Delegát na metodu, která vrátí element na základě jeho adresy (pointer na řádek a element).
	/// Výkonná metoda je typicky ve třídě GridDataCls.GetElementData()
	/// </summary>
	/// <param name="pointer">Ukazatel na řádek a element</param>
	/// <returns>Data elementu</returns>
	internal delegate GridDataElementCls FindElementDataDelegate(DataPointerStr pointer);
	/// <summary>
	/// Předpis delegáta pro event, kterým třída GridDataCls žádá do vizuální vrstvy 
	/// o získání nebo akceptování řádkového filtru.
	/// </summary>
	/// <param name="sender"></param>
	/// <param name="e"></param>
	internal delegate void GridDataRowFilterHandler(object sender, GridDataRowFilterEventArgs e);
	/// <summary>
	/// Předpis delegáta pro event, kterým třída GridDataCls žádá do vizuální vrstvy o překreslení dat
	/// (a/nebo o reload dat).
	/// </summary>
	/// <param name="sender"></param>
	/// <param name="e"></param>
	internal delegate void GridDataCallPaintHandler(object sender, GridDataCallPaintEventArgs e);
	/// <summary>
	/// Předpis delegáta pro event, kterým třída GridGraphCls oznamuje někam nahoru událost na vizuální nebo datové vrstvě.
	/// </summary>
	/// <param name="sender"></param>
	/// <param name="e"></param>
    internal delegate void GridGraphEventHandler(object sender, GridGraphEventArgs e);
    internal delegate void GridGraphToolTipHandler(object sender, GridGraphToolTipEventArgs e); 
	/// <summary>
	/// Základní třída EventArg pro události v grafu
	/// </summary>
	internal class GridEventArgs : EventArgs
	{
		/// <summary>
		/// ID grafu, který akci vyvolává
		/// </summary>
		internal int SourceGraphID { get; private set; }
		/// <summary>
		/// FirstGID grafu, určuje typ grafu (třídu zobrazených dat)
		/// </summary>
		internal GID SourceFirstGId { get; private set; }
		/// <summary>
		/// Režim grafu
		/// </summary>
		internal RowGraphMode SourceGraphMode { get; private set; }
		/// <summary>
		/// Druh události
		/// </summary>
		internal GraphEventType EventType { get; private set; }
		/// <summary>
		/// Konstruktor
		/// </summary>
		/// <param name="graphID">ID grafu</param>
		/// <param name="firstGId">GID první položku, určuje datovou třídu</param>
		/// <param name="graphMode">Default typ grafu</param>
		internal GridEventArgs(int graphID, GID firstGId, RowGraphMode graphMode, GraphEventType eventType)
		{
			this.SourceGraphID = graphID;
			this.SourceFirstGId = firstGId;
			this.SourceGraphMode = graphMode;
			this.EventType = eventType;
		}
	}
	/// <summary>
	/// Třída, kterou GridGraphCls předává informace o elementu, na který uživatel kliknul.
	/// </summary>
	internal class GridGraphEventArgs : GridEventArgs
	{
		/// <summary>
		/// Data elementu, který je předmětem eventu
		/// </summary>
		internal IDataElement DataElement { get; set; }
		/// <summary>
		/// Vizuální index řádku, na němž se nachází element. Jednoznačné určení pro řádky, které se vyskytují opakovaně.
		/// </summary>
		internal int RowIndex { get; set; }
		/// <summary>
		/// Data řádku, na němž se nachází element
		/// </summary>
		internal IDataRow DataRow { get; set; }
		/// <summary>
		/// GID vstupního řádku
		/// </summary>
		internal GID SourceRowGId { get { return (this.DataRow == null ? GID.Empty : this.DataRow.GId); } }
		/// <summary>
		/// GID vstupního elementu
		/// </summary>
		internal GID SourceElementGId { get { return (this.DataElement == null ? GID.Empty : this.DataElement.GId); } }
		/// <summary>
		/// Datapointer vstupního řádku a elementu
		/// </summary>
		internal DataPointerStr SourcePointer { get { return new DataPointerStr(this.SourceRowGId, this.SourceElementGId); } }
		/// <summary>
		/// Konstruktor
		/// </summary>
		/// <param name="graphID"></param>
		/// <param name="firstGId"></param>
		/// <param name="graphMode"></param>
		/// <param name="eventType"></param>
		/// <param name="activityMode"></param>
		internal GridGraphEventArgs(int graphID, GID firstGId, RowGraphMode graphMode, GraphEventType eventType)
			: base(graphID, firstGId, graphMode, eventType)
		{
			this.RowIndex = -1;
			this.DataRow = null;
			this.DataElement = null;
		}
		/// <summary>
		/// Konstruktor
		/// </summary>
		/// <param name="eventType"></param>
		internal GridGraphEventArgs(GraphEventType eventType)
			: base(0, GID.Empty, RowGraphMode.NoDrawingElements, eventType)
		{
			this.RowIndex = -1;
			this.DataRow = null;
			this.DataElement = null;
		}
	}
	/// <summary>
	/// Třída argumentů pro handlery událostí, 
	/// kdy datová vrstva žádá do vizuální vrstvy o načtení nebo o akceptování (tj. zobrazení) uživatelského řádkového filtru.
	/// </summary>
	internal class GridDataRowFilterEventArgs : GridEventArgs
	{
		internal GridDataRowFilterEventArgs()
			: base(0, GID.Empty, RowGraphMode.NoDrawingElements, GraphEventType.None)
		{
		}
		internal GridDataRowFilterEventArgs(RowFilter rowFilter, bool runRedraw)
			: base(0, GID.Empty, RowGraphMode.NoDrawingElements, GraphEventType.None)
		{
			this.RowFilter = rowFilter;
			this.RunRedraw = runRedraw;
		}
		internal RowFilter RowFilter { get; set; }
		internal bool RunRedraw { get; set; }
	}
	/// <summary>
	/// Třída argumentů pro handlery událostí, kdy datová vrstva žádá do vizuální vrstvy o překreslení nebo přenačtení dat konkrétních řádků.
	/// </summary>
	internal class GridDataCallPaintEventArgs : GridEventArgs
	{
		internal GridDataCallPaintEventArgs(int graphID, GID firstGId, RowGraphMode graphMode, List<int> rowIndexList)
			: base(graphID, firstGId, graphMode, GraphEventType.None)
		{
			this.RowIndexList = rowIndexList;
            this.FirstVisibleRowIndex = -1;
            this.FirstVisibleRowPixel = -1;
        }
		internal GridDataCallPaintEventArgs(List<int> rowIndexList)
			: base(0, GID.Empty, RowGraphMode.NoDrawingElements , GraphEventType.None)
		{
			this.RowIndexList = rowIndexList;
            this.FirstVisibleRowIndex = -1;
            this.FirstVisibleRowPixel = -1;
		}
        internal GridDataCallPaintEventArgs(int? activeRowIndex)
            : base(0, GID.Empty, RowGraphMode.NoDrawingElements, GraphEventType.None)
        {
            this.ActiveRowIndex = activeRowIndex;
            this.RowIndexList = null;
            this.FirstVisibleRowIndex = -1;
            this.FirstVisibleRowPixel = -1;
        }
        /// <summary>
		/// Konstruktor
		/// </summary>
		/// <param name="drawAllGraph">Příznak, zda žádáme o vykreslení celého grafu</param>
		internal GridDataCallPaintEventArgs(bool drawAllGraph)
			: base(0, GID.Empty, RowGraphMode.NoDrawingElements, GraphEventType.None)
		{
			this.DrawAllGraph = drawAllGraph;
            this.FirstVisibleRowIndex = -1;
            this.FirstVisibleRowPixel = -1;
        }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="drawAllGraph">Příznak, zda žádáme o vykreslení celého grafu</param>
        internal GridDataCallPaintEventArgs(bool drawAllGraph, int firstVisibleRowIndex, int firstVisibleRowPixel)
            : base(0, GID.Empty, RowGraphMode.NoDrawingElements, GraphEventType.None)
        {
            this.DrawAllGraph = drawAllGraph;
            this.FirstVisibleRowIndex = firstVisibleRowIndex;
            this.FirstVisibleRowPixel = firstVisibleRowPixel;
        }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="drawAllGraph">Příznak, zda žádáme o vykreslení celého grafu</param>
        internal GridDataCallPaintEventArgs(bool drawAllGraph, int firstVisibleRowIndex, int firstVisibleRowPixel, int? activeRowIndex)
            : base(0, GID.Empty, RowGraphMode.NoDrawingElements, GraphEventType.None)
        {
            this.DrawAllGraph = drawAllGraph;
            this.FirstVisibleRowIndex = firstVisibleRowIndex;
            this.FirstVisibleRowPixel = firstVisibleRowPixel;
            this.ActiveRowIndex = activeRowIndex;
        }
        /// <summary>
        /// První viditelný řádek, který se má vykreslit (pokud možno).
        /// Anebo -1, pokud se nemá zohlednit.
        /// </summary>
        internal int FirstVisibleRowIndex { get; private set; }
        /// <summary>
        /// První pixel viditelného řádku, který má být viditelný (pokud možno).
        /// Hodnota 0 = horní okraj konkrétního řádku na indexu FirstVisibleRowIndex, nikoli logická pozice 0 celého grafu. Je to relativní Y daného řádku.
        /// Anebo -1, pokud se nemá zohlednit.
        /// </summary>
        internal int FirstVisibleRowPixel { get; private set; }
        /// <summary>
        /// Seznam indexů řádků, jichž se event týká. Nebo null, když nejde o seznam řádků.
        /// </summary>
        internal List<int> RowIndexList { get; private set; }
        /// <summary>
        /// Index řádku, který má být aktivní. Nebo null, pokud se nemá měnit.
        /// </summary>
        internal int? ActiveRowIndex { get; private set; }
        /// <summary>
		/// Vykreslit celý graf
		/// </summary>
		public bool DrawAllGraph { get; set; }
	}
    /// <summary>
    /// Třída argumentů pro handlery událostí, kdy datová vrstva žádá do vizuální vrstvy o překreslení nebo přenačtení dat konkrétních řádků.
    /// </summary>
    internal class GridGraphToolTipEventArgs : GridGraphEventArgs
    {
        /// <summary>
        /// Text tooltipu
        /// </summary>
        internal string ToolTipText { get; set; }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="target"></param>
        /// <param name="eventType"></param>
        /// <param name="toolTipText"></param>
        internal GridGraphToolTipEventArgs(GridMouseTargetCls target, GraphEventType eventType, string toolTipText)
            : base(0, GID.Empty, RowGraphMode.NoDrawingElements, eventType)
        {
            this.DataRow = target.RowItem;
            this.RowIndex = target.RowIndex;
            this.DataElement = target.DataElement;
            this.ToolTipText = toolTipText;
        }
    }
    /// <summary>
    /// Stavy kolekce subnodů.
    /// </summary>
    internal enum SubNodesStates
    {
        None = 0,
        /// <summary>
        /// Subnody neexistují. Tvrdí to datový zdroj při deklaraci řádku, anebo jsme to zjistili tak, že jsme o subnody požádali a žádný se nevrátil.
        /// </summary>
        NotExists,
        /// <summary>
        /// Subnody nejspíš existují, ale nejsou dosud načteny. Zobrazuje se ikona [+], po kliknutí na ni dojde k načítání řádků.
        /// </summary>
        NotLoaded,
        /// <summary>
        /// Právě probíhá načítání subnodů pro tento node. 
        /// V tuto chvíli je možno nody zavřít i otevřít, ale nespustí se žádné další donačítání.
        /// Pokud v tuto chvíli dojde k invalidaci seznamu subnodů (viz stav Invalid), nelze nastavit stav Invalid, ale stav WasteLoading (zahodit příští načtené řádky a nastavit stav Invalid).
        /// </summary>
        NowLoading,
        /// <summary>
        /// Řádky SubNodes jsou nevalidní. Při nejbližší příležitosti dojde k načtení platných řádků z datového zdroje.
        /// To bude při otevření nodu, nebo při zobrazování některých ze subnodů.
        /// Pokud je tento node zavřený a obsahuje nevalidní řádky, nebude se spouštět jejich načítání.
        /// Při otevření tohoto node, pokud obsahuje nevalidní řádky, budou tyto řádky zobrazeny, současně bude vydán asynchronní požadavek na načtení platných řádků,
        /// a do jeho vyřízení budou tyto nevalidní řádky viditelné. Po dokončení požadavku budou tyto řádky odstraněny a namísto nich budou načteny řádky validní.
        /// </summary>
        Invalid,
        /// <summary>
        /// Stav, kdy probíhá načítání řádků (asynchronní proces), a přitom došlo k invalidaci řádků.
        /// Očekávaný výsledek tohoto načítání je tedy určen k zahození, poté stav přejde do stavu Invalid (následně bude vyžádáno nové čtení).
        /// </summary>
        WasteLoading,
        /// <summary>
        /// Data subnodů jsou načtena a platná, není nutno nic načítat. Nic se ani nenačítá.
        /// </summary>
        Loaded
    }
    /// <summary>
    /// Druh Tree ikony, kterou je vhodné u tohoto nodu zobrazit (nic, plus, minus)
    /// </summary>
    internal enum SubNodesTreeIconType
    {
        None = 0,
        Plus,
        Minus
    }
	#endregion
}
